這幾天陸續看了 Hydration 和 Streaming,也去了解到 Hydration 有可能會造成什麼樣的效能問題,以及可以用什麼方法處理 Hydration 的效能問題,也看了 Streaming 是在解決什麼效能問題。今天再來看看從不同層面的角度去優化效能問題的其他方法,也就是 Partial Prerendering。
Partial Prerendering 是 Next.js App Router 中的一個還在實驗性的功能,簡稱為 PPR。從中文意思來看的話,是「部分預渲染」的意思。也就是說「在一個完整的畫面之中,會有部分的區塊會在 build 的階段以預渲染的方式先準備起來,其他需要動態渲染的部分,則會在發送 request 的時候才進行渲染,最後會將預先渲染的部分和動態渲染的部分結合在一起返回給瀏覽器」。
簡單來說,這個功能結合了靜態生成(SSG)和 React Server Components(RSC)的優勢,讓頁面中不同的部分可以選擇不同的渲染時機,以達到更快的載入速度與更高的互動效率一種渲染機制。
在看要怎麼使用 Partial Prerendering 之前,我們先來思考一下,為什麼需要 Partial Prerendering?
在 Partial Prerendering 出現之前,在 Next.js 中,不論是使用 Page Router,還是使用 App Router,都會以「路由頁面」為單位決定要使用哪種渲染模式。如果整個頁面都是純靜態的內容,就會在 build 的階段先被產出,以 SSG 的方式進行渲染。如果頁面中有動態的內容,即使有部分元件是純靜態的元件,還是會以動態渲染的方式來處理,換句話說,也就是會以 SSR 的方式下去處理。
當一個頁面只有一小個區塊需要動態渲染,但是大部分的內容都是靜態元件時,需要統一使用 SSR 下去做處理,就會讓伺服器去承受大部分其實可以略過的渲染負擔。這時候就非常適合使用 Partial Prerendering 提供的功能,也就是說讓頁面內的多個元件,可以依照是靜態還是動態來決定要使用哪種渲染方式,而不是以頁面為單位決定要使用哪種渲染方式。
總結來說,Partial Prerendering 本身是一種可以減少伺服器所耗效能的渲染方式。如果想讓 Partial Prerendering 除了減少伺服器所耗效能外,還帶有優化 FCP(First Contentful Paint) 的效果,則需要額外搭配 Streaming 的使用。
由於目前這個功能還在實驗階段,所以要使用之前,需要在透過設定 next.config.js 才能啟用。
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
除此之外,還需要在要使用的路由檔案上,透過加上以下的內容,才能真正啟用 Partial Prerendering。
export const experimental_ppr = true
由於 Partial Prerendering 主要是將一個頁面以元件為單位依照實際需求,個別透過靜態渲染和動態渲染來處理,最後再將每個部分的內容組合起來,以達優化頁面效能的效果。所以路由對應的單個頁面中,會同時存在著靜態元件和動態元件。
這裡也來看一個實際使用的範例。
首先準備一個純靜態的元件和一個動態的元件。
// 靜態的元件
const TopContent = () => {
return (
<header style={{ padding: 12, border: "1px solid #ddd", borderRadius: 8 }}>
<h1>⭐ 靜態內容(可預渲染)</h1>
<p>這段會在首屏 HTML 直接出現。</p>
</header>
);
};
export default TopContent;
// 動態的元件
import { cookies } from "next/headers";
async function wait(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
const BottomContent = async () => {
// 使用 cookies() 會讓這個元件成為動態元件
const ck = cookies();
// 模擬慢資料(例如打 API)
await wait(1500);
return (
<section style={{ padding: 12, border: "1px solid #ddd", borderRadius: 8 }}>
<h2>‼️ 動態內容(串流補上)</h2>
<p>時間:{new Date().toLocaleString()}</p>
</section>
);
};
export default BottomContent;
接著這樣實際去使用。
import { Suspense } from "react";
import BottomContent from "./components/BottomContent";
import TopContent from "./components/TopContent";
export const experimental_ppr = true;
const Page = () => {
return (
<>
<TopContent />
<Suspense
fallback={
<section
style={{ padding: 12, border: "1px dashed #aaa", borderRadius: 8 }}
>
<h2>載入動態資訊中…</h2>
</section>
}
>
<BottomContent />
</Suspense>
</>
);
};
export default Page;
在 build 的時候,可以發現結果會顯示成 (Partial Prerender) prerendered as static content prerendered as static HTML with dynamic server-streamed content
。
而且可以查看到 build 的時候就預先產出的 HTML 檔案,HTML 中已經有靜態元件的內容。
以實際效果來看的話,就會呈現以下這個樣子。
接著我們看看 Partial Prerendering 與 Streaming 的差異。
雖然 Partial Prerendering 和 Streaming 都帶有將畫面切成一個個小區塊來處理的概念,但是兩者在把頁面切成小區塊進行處理的這部分帶有不同層級。Partial Prerendering 是在 build 階段就進行前置的處理,而 streaming 則是在把頁面 HTML response 回瀏覽器的階段進行分區塊回傳給瀏覽器的處理。
所以 Partial Prerendering 與 Streaming 處理的效能問題並不相同,Partial Prerendering 是除了能優化 FCP,還能減少伺服器的渲染負擔,而 Streaming 則是主要用來優化 FCP,讓使用者能快一點看到頁面內容。雖然兩者實際上處理的效能問題不同,但是因為 Partial Prerendering 是在 build 階段進行效能處理的策略,而 Streaming 則是在使用者發送請求後,返回 HTML 階段進行效能的優化處理,所以這兩者其實可以搭配使用。進而達到減少伺服器負擔,以及優化使用者體驗的效果。
以上就是關於 PPR 的內容,明天我們會稍微轉換一下口味(?),再次回到 SSR、SSG 等渲染模式有關的內容。
那就明天見囉!